﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Krile.Kernel.Bridges;
using Krile.Kernel.Plugin;
using Krile.Kernel.Data;
using Krile.Data;
using Krile.Subsystem;
using Krile.Kernel.WindowsEx;
using System.Runtime.InteropServices;

namespace Krile.Forms.MainForm
{
    public partial class Sub : Form, IFormEx
    {
        public Controls.XTab.XTabWindowItem LinkingWindowTab { get; set; }

        public Sub()
        {
            InitializeComponent();
            this.Enter += new EventHandler(Sub_Enter);
            this.Leave += new EventHandler(Sub_Leave);
            this.xTab.IsWindowTab = false;
        }

        public Controls.XTab.XTab TabControl { get { return xTab; } }

        public Controls.Surface.Surface Surface { get { return surface; } }

        internal Data.TabData[] TabDatas
        {
            get
            {
                var dat = from d in xTab.GetItemBaseArray()
                          where d is Controls.XTab.XTabTLItem
                          select ((Controls.XTab.XTabTLItem)d).TabData;
                return dat.ToArray<Data.TabData>();
            }
        }

        public bool Closable = false;

        public void EnforceClose()
        {
            Closable = true;
            this.Invoke(new Action(base.Close));
        }

        public void RegistTab(Controls.XTab.XTabItem item)
        {
            this.xTab.AddTab(item);
            item.Selected = true;
            Core.SaveConfig(false);
        }

        public void RemoveTab(Controls.XTab.XTabItem item)
        {
            xTab.RemoveTab(item);
            if (xTab.GetTabItemCount() == 0)
            {
                this.EnforceClose();
            }
        }

        private void Sub_FormClosing(object sender, FormClosingEventArgs e)
        {
            switch (e.CloseReason)
            {
                case CloseReason.None:
                case CloseReason.UserClosing:
                    if (!Closable && Core.Config.Behavior.ConfirmOnCloseWindow)
                    {
                        bool never;
                        var ret = MessageBoxEx.Show(
                            Lang.Msg.WindowClose,
                            Lang.Msg.WindowClose_Caption,
                            MessageBoxEx.MessageBoxExButtons.YesNo, MessageBoxEx.MessageBoxExIcon.Warning, out never);
                        Core.Config.Behavior.ConfirmOnCloseWindow = !never;

                        if (ret == MessageBoxEx.DialogResultEx.No)
                        {
                            e.Cancel = true;
                            return;
                        }
                        var dat = from d in xTab.GetItemBaseArray()
                                  where d is Controls.XTab.XTabTLItem
                                  select (Controls.XTab.XTabTLItem)d;

                        if (dat != null)
                        {
                            foreach (var d in dat)
                            {
                                TabControl.CloseTab(d);
                            }
                        }
                    }
                    if (MdiParent != null)
                        MdiParent.RemoveOwnedForm(this);
                    Core.FormManager.RemoveSubForm(this);
                    this.Dispose();
                    break;
                default:
                    System.Diagnostics.Debug.WriteLine("Close reason:" + e.CloseReason.ToString());
                    if (Closable)
                    {
                        if (MdiParent != null)
                            MdiParent.RemoveOwnedForm(this);
                        Core.FormManager.RemoveSubForm(this);
                        this.Dispose();
                    }
                    else
                    {
                        e.Cancel = true;
                    }
                    break;
            }
        }

        public void SetCursor(Cursor csor)
        {
            surface.Cursor = csor;
        }

        public void DeselectStatus()
        {
            this.surface.SelectedIndex = -1;
        }

        public Status SelectedStatus
        {
            get
            {
                if (surface.SelectedStatus == null)
                    return null;
                else
                    return surface.SelectedStatus.BaseStatus;
            }
        }

        EventHandler tlCsChanged;
        Controls.XTab.XTabTLItem prevSelected = null;

        private string OrigText = Define.ApplicationName;

        private void xTab_OnSelectedTabChanged(object sender, EventArgs e)
        {
            ApplyTabChanged(true, true);
            Core.FormManager.MainForm.WindowsTabControl.Refresh();
        }

        EventHandler serviceChanged = null;
        public void ApplyTabChanged(bool deactivate, bool activate)
        {
            if (serviceChanged == null)
                serviceChanged = new EventHandler(ServiceChanged);
            if (deactivate)
            {
                //for inputter relations, and
                //Update current statuses.
                if (prevSelected != null)
                {
                    prevSelected.ScrollOffset = surface.ScrollValue - Core.Config.Appearance.Renderer.Detail.NormalListHeight * surface.SelectedIndex;
                    //if prevSelected.TabData is null, this tab had been closed.
                    if (prevSelected.TabData != null)
                    {
                        prevSelected.TabData.ContainStatusChanged -= tlCsChanged;
                        Core.FormManager.MainForm.InputMethodManager.OnSelectedServicesChanged -= serviceChanged;
                    }
                }
            }

            if (activate)
            {
                prevSelected = (Controls.XTab.XTabTLItem)xTab.SelectedItem;

                if (prevSelected != null)
                {
                    this.Text = prevSelected.Name;
                    surface.Deselect(false);
                    prevSelected.TabData.ContainStatusChanged += tlCsChanged;
                    UpdateCurrentStatuses(
                        prevSelected.TabData.ContainStatuses,
                        prevSelected.TabData.SelectedStatus,
                        prevSelected.TabData.ScrollOffset);
                    if (String.IsNullOrEmpty(Core.FormManager.MainForm.InputMethodManager.GetInputText()) &&
                        Core.FormManager.MainForm.InputMethodManager.GetReplyTo() == 0)
                    {
                        if (prevSelected.TabData.LinkedServiceIds != null)
                        {
                            var svcs = from id in prevSelected.TabData.LinkedServiceIds
                                       where Core.PluginManager.LookupService(id) != null
                                       select Core.PluginManager.LookupService(id);
                            Core.FormManager.MainForm.InputMethodManager.SelectedServices = svcs.ToArray<Krile.Kernel.Data.Service>();

                        }
                        else
                        {
                            Core.FormManager.MainForm.InputMethodManager.SelectedServices = null;
                        }
                    }
                    Core.FormManager.MainForm.InputMethodManager.OnSelectedServicesChanged += serviceChanged;
                }
                else
                {
                    this.Text = OrigText;
                    UpdateCurrentStatuses(null, 0, 0);
                    surface.Statuses = null;
                    surface.ScrollValue = 0;
                    surface.InitScrollbar();
                    Core.FormManager.MainForm.InputMethodManager.SelectedServices = null;
                }
            }
            else
                prevSelected = null;
        }

        void ServiceChanged(object sender, EventArgs e)
        {
            if (prevSelected == null) return;
            if (Core.FormManager.MainForm.InputMethodManager.SelectedServices != null)
            {
                var svd = from svc in Core.FormManager.MainForm.InputMethodManager.SelectedServices
                          select svc.Id;
                prevSelected.TabData.LinkedServiceIds = svd.ToArray<string>();
            }
            else
            {
                prevSelected.TabData.LinkedServiceIds = null;
            }
        }

        void ApplyCurrentOptional()
        {
            if (SelectedStatus != null)
                UpdateCurrentStatuses(curRoot, SelectedStatus.UID, surface.ScrollValue - surface.SelectedIndex * Core.Config.Appearance.Renderer.Detail.NormalListHeight);
            else
                UpdateCurrentStatuses(curRoot, 0, 0);
        }

        IEnumerable<Data.XStatus> curRoot;
        Func<Data.XStatus, bool> currentOptional = null;

        ulong csid = 0;
        object StatusUpdateLock = new object();
        object BeginInvokeReleaseLock = new object();
        void UpdateCurrentStatuses(IEnumerable<XStatus> statuses, ulong selected, long offset)
        {
            curRoot = statuses;
            var query = currentOptional;
            if (query != null && searchBar.SearchActivated)
            {
                lock (BeginInvokeReleaseLock)
                {
                    csid++;
                    Action<IEnumerable<XStatus>, ulong, long, ulong> act = new Action<IEnumerable<XStatus>, ulong, long, ulong>(StatusFilter);
                    act.BeginInvoke(statuses, selected, offset, csid, FinishFilter, act);
                }
            }
            else
            {
                if (selected != 0)
                    surface.UpdateStatusWithSelectionPoint(statuses, selected, offset);
                else
                    surface.Statuses = statuses.MassiveToArray();
            }
        }

        void StatusFilter(IEnumerable<XStatus> statuses, ulong selected, long offset, ulong myId)
        {
            var query = currentOptional;
            if (query == null || statuses == null)
            {
                surface.Statuses = statuses.ToArray();
                return;
            }
            List<XStatus> xss = new List<XStatus>();
            foreach (var s in statuses)
            {
                if (s.BaseStatus != null && query(s))
                    xss.Add(s);
                if (csid != myId)
                    break;
            }
            if (csid == myId)
            {
                this.SafeOperate(() =>
                {
                    if (selected != 0)
                        surface.UpdateStatusWithSelectionPoint(xss, selected, offset);
                    else
                        surface.Statuses = xss.ToArray();
                });
            }
        }

        void FinishFilter(IAsyncResult e)
        {
            ((Action<IEnumerable<XStatus>, ulong, long, ulong>)e.AsyncState).EndInvoke(e);
        }

        private void xTab_OnClosedTab(Controls.XTab.XTabItem obj)
        {
            Core.FormManager.MainForm.PushClosedTab(obj);
            if (xTab.GetTabItemCount() == 0)
                this.EnforceClose();
            Core.SaveConfig(false);
        }

        delegate void RefreshTLInvoker(bool remeasure);
        public void RefreshTL(bool remeasure)
        {
            this.SafeOperate<bool>((b) => surface.SurfaceRedraw(b), remeasure);
        }

        public void RefreshTLWeak(bool remeasure)
        {
            surface.RefreshWeak(remeasure);
        }

        public void EnsureVisible()
        {
            surface.EnsureVisible();
        }

        private void Sub_Load(object sender, EventArgs e)
        {
            Core.KeyAssign.SetKeyEvent(this);
            tlCsChanged = new EventHandler(TabData_ContainStatusChanged);

            //title init
            this.Text = this.Text + " " + Define.VersionString;

            surface.InitScrollbar();

            if (Core.Config.State.SearchBarBottom)
                searchBar.Dock = DockStyle.Bottom;

            this.MouseWheel += new MouseEventHandler(Sub_MouseWheel);
        }

        void TabData_ContainStatusChanged(object sender, EventArgs e)
        {
            if (prevSelected != null)
                UpdateCurrentStatuses(prevSelected.TabData.ContainStatuses, 0, 0);
        }

        void Sub_MouseWheel(object sender, MouseEventArgs e)
        {
            //スクロールバー消滅対策用
        }

        public void AddInheritedTabAutoFormat(string extractSearchName, Krile.Kernel.Data.SearchDataBase[] addSearchDatas)
        {
            string bdname = Core.Config.Appearance.TabAppearance.ATLTabName;
            if (xTab.SelectedItem != null)
            {
                var baseData = ((Forms.MainForm.Controls.XTab.XTabTLItem)xTab.SelectedItem).TabData;
                if (baseData != null)
                    bdname = baseData.Name;
            }
            AddInheritedTab(
                String.Format(Lang.QuickTab.TabImportStr, extractSearchName, bdname), addSearchDatas);
        }

        public void AddInheritedTab(string name, Krile.Kernel.Data.SearchDataBase[] addSearchDatas)
        {
            if (xTab.SelectedItem == null)
            {
                AddTab(name, new Krile.Kernel.Data.SearchDataCluster(addSearchDatas), null);
            }
            else
            {
                var baseData = ((Forms.MainForm.Controls.XTab.XTabTLItem)xTab.SelectedItem).TabData;
                var newData = new Krile.Data.TabData();
                newData.Name = String.Format(name, baseData.Name);
                newData.UnNotify = baseData.UnNotify;
                List<Krile.Kernel.Data.SearchDataBase> sdbArray = new List<Krile.Kernel.Data.SearchDataBase>();
                if (baseData.ContainSearchData != null)
                    sdbArray.Add(baseData.ContainSearchData);
                sdbArray.AddRange(addSearchDatas);
                newData.ContainSearchData = new Krile.Kernel.Data.SearchDataCluster(sdbArray.ToArray());
                xTab.AddTab(new Controls.XTab.XTabTLItem(this.xTab, newData));
            }
        }

        public void CreateTab(string name, Krile.Kernel.Data.SearchDataCluster cluster, bool unNotify, string[] svids)
        {
            var ntd = new Data.TabData();
            ntd.ContainSearchData = cluster;
            ntd.Name = name;
            ntd.UnNotify = unNotify;
            ntd.LinkedServiceIds = svids;
            xTab.AddTab(
                new MainForm.Controls.XTab.XTabTLItem(
                    xTab,
                    ntd));
            Core.SaveConfig(false);
        }

        public void AddTab(string name, Krile.Kernel.Data.SearchDataCluster cluster, string[] svids)
        {
            this.CreateTab(name, cluster, false, svids);
        }

        public bool SetSelectStatus(SearchTarget.SelectTargets target)
        {
            switch (target)
            {
                case SearchTarget.SelectTargets.Above:
                    if (surface.Statuses == null || surface.SelectedIndex < 0) return false;
                    surface.SelectedIndex--;
                    return true;
                case SearchTarget.SelectTargets.Below:
                    if (surface.Statuses == null || surface.SelectedIndex >= surface.Statuses.Length - 1) return false;
                    surface.SelectedIndex++;
                    return true;
                case SearchTarget.SelectTargets.AboveNew:
                    if (surface.Statuses == null || surface.SelectedIndex < 0) return false;
                    for (long i = surface.SelectedIndex; i >= 0; i--)
                    {
                        if (surface.Statuses[i].Unread)
                        {
                            surface.SelectedIndex = i;
                            return true;
                        }
                    }
                    return false;
                case SearchTarget.SelectTargets.BelowNew:
                    if (surface.Statuses == null || surface.SelectedIndex >= surface.Statuses.Length - 1) return false;
                    for (long i = surface.SelectedIndex; i < surface.Statuses.Length - 1; i++)
                    {
                        if (i < 0) continue;
                        if (surface.Statuses[i].Unread)
                        {
                            surface.SelectedIndex = i;
                            return true;
                        }
                    }
                    return false;
                case SearchTarget.SelectTargets.Deselect:
                    surface.SelectedIndex = -1;
                    return true;
                case SearchTarget.SelectTargets.Top:
                    if (surface.Statuses == null) return false;
                    surface.SelectedIndex = 0;
                    return true;
                case SearchTarget.SelectTargets.End:
                    if (surface.Statuses == null) return false;
                    surface.SelectedIndex = surface.Statuses.Length - 1;
                    return true;
                case SearchTarget.SelectTargets.Newest:
                    if (surface.Statuses == null) return false;
                    if (Core.Config.Timeline.ListBottomNewer)
                        surface.SelectedIndex = surface.Statuses.Length - 1;
                    else
                        surface.SelectedIndex = 0;
                    return true;
                default:
                    throw new NotSupportedException("target is not supported:" + Enum.GetName(typeof(SearchTarget.SelectTargets), target));
            }
        }

        public void SetScrollState(SearchTarget.SelectTargets target)
        {
            switch (target)
            {
                case SearchTarget.SelectTargets.Above:
                    int v = surface.VerticalPosition;
                    v -= surface.Height;
                    surface.SetScrollTo(v);
                    break;
                case SearchTarget.SelectTargets.Below:
                    int w = surface.VerticalPosition;
                    w += surface.Height;
                    surface.SetScrollTo(w);
                    break;
                case SearchTarget.SelectTargets.Top:
                    surface.SetScrollTo(0);
                    break;
                case SearchTarget.SelectTargets.End:
                    surface.SetScrollTo(surface.VerticalPositionMax);
                    break;
                default:
                    throw new NotSupportedException("target is not supported:" + Enum.GetName(typeof(SearchTarget.SelectTargets), target));
            }
        }

        public void EnsureVisibleTop()
        {
            surface.EnsureVisibleTop();
        }

        public void SetReadedAll()
        {
            surface.DoEnumOperationWithProtectedThread((a) => a.Unread = false);
            surface.Refresh();
        }

        public bool SelectStatusByUID(ulong uid, bool p)
        {
            return surface.SelectStatusByUID(uid, p);
        }

        private void TSearch_Click(object sender, EventArgs e)
        {
            TSearch.Checked = !TSearch.Checked;
            searchBar.Visible = TSearch.Checked;
        }

        private void searchBar_OnChangeVPosRequired(object sender, EventArgs e)
        {
            if (searchBar.Dock == DockStyle.Bottom)
            {
                searchBar.Dock = DockStyle.Top;
            }
            else
            {
                searchBar.Dock = DockStyle.Bottom;
            }
        }

        private void searchBar_OnCloseRequired(object sender, EventArgs e)
        {
            SetSearchbarVisible(false);
        }

        private void searchBar_OnNewTabCreateRequired(object sender, EventArgs e)
        {
            string bdname = Core.Config.Appearance.TabAppearance.ATLTabName;
            if (xTab.SelectedItem != null)
            {
                var baseData = ((Forms.MainForm.Controls.XTab.XTabTLItem)xTab.SelectedItem).TabData;
                if (baseData != null)
                    bdname = baseData.Name;
            }
            switch (searchBar.CurrentSearchType)
            {
                case Krile.Forms.MainForm.Controls.SearchBar.SearchTypes.User:
                    AddInheritedTab(
                        String.Format(Lang.QuickTab.TabImportStr, searchBar.SearchNeedle, bdname),
                        new[] { new Data.UserIdSearchData(searchBar.SearchNeedle) });
                    break;
                case Krile.Forms.MainForm.Controls.SearchBar.SearchTypes.Text:
                    AddInheritedTab(
                        String.Format(Lang.QuickTab.TabImportStr, searchBar.SearchNeedle, bdname),
                        new[] { new Data.TextSearchData(searchBar.SearchNeedle) });
                    break;
                default:
                    AddInheritedTab(
                        String.Format(Lang.QuickTab.TabImportStr, searchBar.SearchNeedle, bdname),
                        new[] { new Data.AllSearchData(searchBar.SearchNeedle) });
                    break;
            }
            SetSearchbarVisible(false);
        }

        private void searchBar_OnSearchActivatedChanged(object sender, EventArgs e)
        {
            UpdateSearchKey();
        }

        private void UpdateSearchKey()
        {
            if (searchBar.SearchActivated && !String.IsNullOrEmpty(searchBar.SearchNeedle))
            {
                if (Core.MigemoLib != null && Core.MigemoLib.IsEnable())
                {
                    if (searchBar.SearchNeedle.Length < Core.Config.Advanced.MigemoMinimumLength)
                    {
                        currentOptional = null;
                        return;
                    }
                    switch (searchBar.CurrentSearchType)
                    {
                        case Krile.Forms.MainForm.Controls.SearchBar.SearchTypes.Text:
                            currentOptional = new Func<Krile.Data.XStatus, bool>((s) =>
                                Core.MigemoLib.GetRegex(searchBar.SearchNeedle).IsMatchEx(s.BaseStatus.Text.Original));
                            break;
                        case Krile.Forms.MainForm.Controls.SearchBar.SearchTypes.User:
                            currentOptional = new Func<Krile.Data.XStatus, bool>((s) =>
                                {
                                    var rgx = Core.MigemoLib.GetRegex(searchBar.SearchNeedle);
                                    return
                                        rgx.IsMatchEx(s.BaseStatus.User.Id) ||
                                        rgx.IsMatchEx(s.BaseStatus.User.Name);
                                });
                            break;
                        default:
                            currentOptional = new Func<Krile.Data.XStatus, bool>((s) =>
                                {
                                    var rgx = Core.MigemoLib.GetRegex(searchBar.SearchNeedle);
                                    return
                                        rgx.IsMatchEx(s.BaseStatus.User.Id) ||
                                        rgx.IsMatchEx(s.BaseStatus.User.Name) ||
                                        rgx.IsMatchEx(s.BaseStatus.Text.Original) ||
                                        (s.BaseStatus.ReplyData != null &&
                                        (
                                            rgx.IsMatchEx(s.BaseStatus.ReplyData.ReplyToText) ||
                                            rgx.IsMatchEx(s.BaseStatus.ReplyData.ReplyToUserId) ||
                                            rgx.IsMatchEx(s.BaseStatus.ReplyData.ReplyToUserName)
                                        ));
                                });
                            break;
                    }
                }
                else
                {
                    switch (searchBar.CurrentSearchType)
                    {
                        case Krile.Forms.MainForm.Controls.SearchBar.SearchTypes.Text:
                            currentOptional = new Func<Krile.Data.XStatus, bool>((s) =>
                                s.BaseStatus.Text.Original.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase));
                            break;
                        case Krile.Forms.MainForm.Controls.SearchBar.SearchTypes.User:
                            currentOptional = new Func<Krile.Data.XStatus, bool>((s) =>
                                s.BaseStatus.User.Id.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase) ||
                                s.BaseStatus.User.Name.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase));
                            break;
                        default:
                            currentOptional = new Func<Krile.Data.XStatus, bool>((s) =>
                                s.BaseStatus.User.Id.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase) ||
                                s.BaseStatus.User.Name.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase) ||
                                s.BaseStatus.Text.Original.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase) ||
                                (s.BaseStatus.ReplyData != null &&
                                    (s.BaseStatus.ReplyData.ReplyToText.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase) ||
                                     s.BaseStatus.ReplyData.ReplyToUserId.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase) ||
                                     s.BaseStatus.ReplyData.ReplyToUserName.Contains(searchBar.SearchNeedle, StringComparison.CurrentCultureIgnoreCase)
                                 )
                                 ));
                            break;
                    }
                }
            }
            else
            {
                currentOptional = null;
            }
        }

        private void searchBar_OnSearchNeedleChanged(object sender, EventArgs e)
        {
            UpdateSearchKey();
            ApplyCurrentOptional();
        }

        public bool SearchbarVisible
        {
            get { return searchBar.Visible; }
        }

        public void SetSearchbarVisible(bool visible)
        {
            searchBar.Visible = visible;
            TSearch.Checked = visible;
            if (visible)
            {
                searchBar.SetFocus();
            }
            else
            {
                ApplyCurrentOptional();
                searchBar.Clear();
                surface.Focus();
            }
        }

        public void UpdateTimeline()
        {
            this.xTab_OnSelectedTabChanged(null, null);
        }

        private void Sub_Resize(object sender, EventArgs e)
        {
            this.SubStrip.Visible = (this.WindowState != FormWindowState.Maximized);
        }

        void Sub_Leave(object sender, EventArgs e)
        {
            ApplyTabChanged(true, false);
        }

        void Sub_Enter(object sender, EventArgs e)
        {
            Core.FormManager.MainForm.PreviousForegroundForm = this;
            ApplyTabChanged(false, true);
        }

        public HandleRef hWndHandle
        {
            get { return new HandleRef(this, this.Handle); }
        }
    }
}
